
import { Object3D } from "../../../entity/Object3D.js";
import { OBB } from "./Tiles/geometry/obb.js";
import { path } from "./Tiles/utils/path.js"
import { resolveImplicite } from './implicit/ImplicitTileResolver.js';
import { Sphere } from "../../../core/math/Sphere.js";
import { Vector2 } from "../../../core/math/Vector2.js";
import { Vector3 } from "../../../core/math/Vector3.js";
import { Quaternion } from "../../../core/math/Quaternion.js";
import { generateUUID } from "../../../core/math/MathUtils.js";
import { Matrix4 } from "../../../core/math/Matrix4.js";
import { Frustum } from "../../../core/math/Frustum.js";
import { Engine } from "../../../Engine.js";
import { Euler } from "../../../core/math/Euler.js";
import {proj} from "../../../core/crs/proj.js"
const tempSphere = new Sphere(new Vector3(0, 0, 0), 1);
const tempVec1 = new Vector3(0, 0, 0);
const tempVec2 = new Vector3(0, 0, 0);
const upVector = new Vector3(0, 0, 1);
const rendererSize = new Vector2(1000, 1000);
const tempQuaternion = new Quaternion();

class OGC3DTile {
    constructor(properties) {
        if (properties.url) {
            var results = properties.url.split('/');
            this.fileName = results[results.length - 2];
        }
        if (properties.parentTile) {
            this.id = properties.parentTile.id + properties.index
        } else {
            this.id = this.fileName + properties.index
        }
        this.jsonID = properties.jsonID
        let uuid = generateUUID();
        this.uuid = uuid
        const self = this;
        // 本地矩阵
        this.localMat = new Matrix4()
        // 世界矩阵
        this.worldMat = new Matrix4()
        this.tileLoader = properties.tileLoader;
        this.displayErrors = this.tileLoader.displayErrors;
        this.geometricErrorMultiplier = !!properties.geometricErrorMultiplier ? properties.geometricErrorMultiplier : 1.0;
        this.meshCallback = properties.meshCallback;
        this.loadOutsideView = properties.loadOutsideView;
        this.cameraOnLoad = properties.cameraOnLoad;
        this.parentTile = properties.parentTile;

        // 声明特定于tile的属性
        this.childrenTiles = [];
        this.meshContent = [];
        this.tileContent;
        this.refine; // defaults to "REPLACE"
        this.rootPath;
        this.geometricError;
        this.boundingVolume;
        this.json; // 对应于这个tile的json
        this.materialVisibility = false;
        this.inFrustum = true;
        this.level = properties.level ? properties.level : 0;
        this.hasMeshContent = 0; // 当提供的json具有指向B3DM文件的内容字段时，为true
        this.hasUnloadedJSONContent = 0; // 当提供的json具有指向尚未加载的json文件的内容字段时，为true
        this.centerModel = properties.centerModel;
        this.abortController = new AbortController();

        if (!!properties.json) { // 如果此Tile是作为另一个Tile的子Tiles创建的，则properties。Json不是空的
            self.setup(properties);
        } else if (properties.url) { // tileset.jsonn的url   
            var url = properties.url;
            var fetchFunction;
            fetchFunction = () => {
                return fetch(url, { signal: self.abortController.signal });
            }
            fetchFunction().then(result => {
                if (!result.ok) {
                    throw new Error(`不能加载 "${properties.url}". 状态 ${result.status} : ${result.statusText}`);
                }
                result.json().then(json => { return resolveImplicite(json, url) }).then(json => {
                    self.setup({
                        rootPath: path.dirname(properties.url),
                        json: json,
                        parentRefine: properties.parentRefine,
                        parentGeometricError: properties.parentGeometricError,
                        parentBoundingVolume: properties.parentBoundingVolume,
                        onLoadCallback: properties.onLoadCallback
                    });
                });
            }).catch(e => { if (self.displayErrors) showError(e) });
        }
    }

    async setup(properties) {
        const self = this;
        if (!!properties.json.root) {
            self.json = properties.json.root;
            if (!self.json.refine) self.json.refine = properties.json.refine;
            if (!self.json.geometricError) self.json.geometricError = properties.json.geometricError;
            if (!self.json.transform) self.json.transform = properties.json.transform;
            if (!self.json.boundingVolume) self.json.boundingVolume = properties.json.boundingVolume;
        } else {
            self.json = properties.json;
        }
        if (!self.json.children) {
            if (self.json.getChildren) {
                self.json.children = await self.json.getChildren();
            } else {
                self.json.children = [];
            }
        }
        self.rootPath = !!properties.json.rootPath ? properties.json.rootPath : properties.rootPath;
        // 解析 refine
        if (!!self.json.refine) {
            self.refine = self.json.refine;
        } else {
            self.refine = properties.parentRefine;
        }
        // 解析 geometric error
        if (!!self.json.geometricError) {
            self.geometricError = self.json.geometricError;
        } else {
            self.geometricError = properties.parentGeometricError;
        }
        // 解析 transform
        if (!!self.json.transform && !self.centerModel) {
            this.localMat.elements = self.json.transform
            if(this.tileLoader.crs==="4326"){
                let coord84=proj("EPSG:4479","EPSG:4326",[this.localMat.elements[12],this.localMat.elements[13],this.localMat.elements[14]])
                let  scalexy=  proj.scale([coord84[0],coord84[1]])
                let scale=[scalexy[0],scalexy[1],110000]
                console.log(scale);
                // coord84[2]=coord84[2]/110000
                console.log(coord84)
                coord84[0]=coord84[0]+this.tileLoader.translation[0]
                coord84[1]=coord84[1]+this.tileLoader.translation[1]
                coord84[2]=this.tileLoader.translation[2]
                let out= new Matrix4()
                out.scale( new Vector3( 1/scale[0],1/scale[1],1/scale[2]))
                out.setPosition(coord84[0],coord84[1],coord84[2])
                this.localMat=out
                this.worldMat = this.worldMat.multiply(this.localMat)
            }else{
                let coord84=proj("EPSG:4479","EPSG:3857",[this.localMat.elements[12],this.localMat.elements[13],this.localMat.elements[14]])
                coord84[0]=coord84[0]+this.tileLoader.translation[0]
                coord84[1]=coord84[1]+this.tileLoader.translation[1]
                coord84[2]=this.tileLoader.translation[2]
                let out= new Matrix4()
                out.scale( new Vector3( 1.085,1.09,1))
                out.setPosition(coord84[0],coord84[1],coord84[2])
                this.localMat=out
                this.worldMat = this.worldMat.multiply(this.localMat)
            }
      
        }
        if (this.parentTile) {
            const w= new Matrix4()
            this.worldMat = w.multiplyMatrices(this.parentTile.worldMat,this.worldMat)
        }
        // 解析 volume
        if (!!self.json.boundingVolume) {
            if (!!self.json.boundingVolume.box) {
                self.boundingVolume = new OBB(self.json.boundingVolume.box);
            } else if (!!self.json.boundingVolume.region) {
                const region = self.json.boundingVolume.region;
                self.transformWGS84ToCartesian(region[0], region[1], region[4], tempVec1);
                self.transformWGS84ToCartesian(region[2], region[3], region[5], tempVec2);
                tempVec1.lerp(tempVec2, 0.5);
                self.boundingVolume = new Sphere(new Vector3(tempVec1.x, tempVec1.y, tempVec1.z), tempVec1.distanceTo(tempVec2));
            } else if (!!self.json.boundingVolume.sphere) {
                const sphere = self.json.boundingVolume.sphere;
                self.boundingVolume = new Sphere(new Vector3(sphere[0], sphere[1], sphere[2]), sphere[3]);
            } else {
                self.boundingVolume = properties.parentBoundingVolume;
            }
        } else {
            self.boundingVolume = properties.parentBoundingVolume;
        }
        function checkContent(e) {
            if (!!e.uri && e.uri.includes("json")) {
                self.hasUnloadedJSONContent++;
            } else if (!!e.url && e.url.includes("json")) {
                self.hasUnloadedJSONContent++;
            } else {
                self.hasMeshContent++;
            }
        }
        if (!!self.json.content) {
            checkContent(self.json.content);
            self.load();
        } else if (!!self.json.contents) {
            self.json.contents.forEach(e => checkContent(e))
            self.load();
        }
        if (!!self.centerModel) {
            const tempSphere = new Sphere();
            if (self.boundingVolume instanceof OBB) {
                // box
                tempSphere.copy(self.boundingVolume.sphere);
            } else if (self.boundingVolume instanceof Sphere) {
                //sphere
                tempSphere.copy(self.boundingVolume);
            }
            if (!!this.json.boundingVolume.region) {
                this.transformWGS84ToCartesian(
                    (this.json.boundingVolume.region[0] + this.json.boundingVolume.region[2]) * 0.5,
                    (this.json.boundingVolume.region[1] + this.json.boundingVolume.region[3]) * 0.5,
                    (this.json.boundingVolume.region[4] + this.json.boundingVolume.region[5]) * 0.5,
                    tempVec1);

                tempQuaternion.setFromUnitVectors(tempVec1.normalize(), upVector.normalize());
                var rotationMatrix = new Matrix4();
                rotationMatrix.makeRotationFromQuaternion(tempQuaternion);
                this.worldMat.multiply(rotationMatrix);
            }
            let translation = new Vector3(-tempSphere.center.x, -tempSphere.center.y, -tempSphere.center.z)
            this.worldMat.setPosition(translation)
        }
        if (properties.onLoadCallback) properties.onLoadCallback(self);
        self.isSetup = true;
    }
    // 组装
    assembleURL(root, relative) {
        // 如果根URL没有斜杠，在其后面添加斜杠
        if (!root.endsWith('/')) {
            root += '/';
        }
        const rootUrl = new URL(root);
        let rootParts = rootUrl.pathname.split('/').filter(p => p !== '');
        let relativeParts = relative.split('/').filter(p => p !== '');
        for (let i = 1; i <= rootParts.length; i++) {
            if (i >= relativeParts.length) break;
            const rootToken = rootParts.slice(rootParts.length - i, rootParts.length).join('/');
            const relativeToken = relativeParts.slice(0, i).join('/');
            if (rootToken === relativeToken) {
                for (let j = 0; j < i; j++) {
                    rootParts.pop();
                }
                break;
            }
        }
        while (relativeParts.length > 0 && relativeParts[0] === '..') {
            rootParts.pop();
            relativeParts.shift();
        }
        return `${rootUrl.protocol}//${rootUrl.host}/${[...rootParts, ...relativeParts].join('/')}`;
    }
    extractQueryParams(url) {
        const urlObj = new URL(url);
        // 删除查询字符串
        urlObj.search = '';
        return urlObj.toString();
    }
    async load() {
        var self = this;
        if (self.deleted) return;
        if (!!self.json.content) {
            await loadContent(self.json.content, null);
        } else if (!!self.json.contents) {
            let promises = self.json.contents.map((content, index) => loadContent(content, index));
            Promise.all(promises)
        }
        async function loadContent(content, contentIndex) {
            let url;
            if (!!content.uri) {
                url = content.uri;
            } else if (!!content.url) {
                url = content.url;
            }
            const urlRegex = /^(?:http|https|ftp|tcp|udp):\/\/\S+/;
            if (urlRegex.test(self.rootPath)) { // url
                if (!urlRegex.test(url)) {
                    url = self.assembleURL(self.rootPath, url);
                }
            } else { //path
                if (path.isAbsolute(self.rootPath)) {
                    url = self.rootPath + path.sep + url;
                }
            }
            url = self.extractQueryParams(url);
            if (!!url) {
                if (url.includes(".b3dm") || url.includes(".glb") || url.includes(".gltf")) {
                    self.contentURL = url;
                    try {
                        self.tileLoader.get(self.abortController, self.uuid, url, mesh => {
                            if (!!self.deleted) return;
                            self.meshId = mesh.id
                            Engine.instance.scene.addEntity(mesh)
                            self.meshContent.push(mesh);
                        }, !self.cameraOnLoad ? () => 0 : () => { return self.calculateDistanceToCamera(self.cameraOnLoad); },
                            () => self.getSiblings(),
                            self.level,
                            self.centerModel?new Matrix4(): self.worldMat
                        );
                    } catch (e) {
                        if (self.displayErrors) showError(e)
                    }

                } else if (url.includes(".json")) {
                    self.tileLoader.get(self.abortController, self.uuid, url, async json => {
                        if (!!self.deleted) return;
                        json.rootPath = path.dirname(url);
                        self.json.children.push(json);
                        if (contentIndex == null) {
                            delete self.json.content;
                        } else {
                            delete self.json.contents.splice(contentIndex, 1);
                        }
                        self.hasUnloadedJSONContent--;
                    });
                }
            }
        }
    }
    traverse(callback) {
        callback(this);
        const children = this.childrenTiles;
        for (let i = 0, l = children.length; i < l; i++) {
            children[i].traverse(callback);
        }
    }
    dispose() {
        const self = this;
        self.childrenTiles.forEach(tile => { tile.dispose() });
        self.deleted = true;
        if (!!self.contentURL) {
            self.tileLoader.invalidate(self.contentURL, self.uuid);
        }
        if (!!self.abortController) { // 中断请求
            self.abortController.abort();
        }
        if (!!self.meshContent) {
            self.meshContent.map(item => {
                Engine.instance.scene.removeEntity(item.id, false)
            })
            self.meshContent=[]
        }
        this.parentTile = null;
    }
    removeXYRotation(complexMatrix){
        const position = new Vector3();
        const scale = new Vector3();
        const quaternion = new Quaternion();
        complexMatrix.decompose(position,quaternion,scale)
        let euler = new Euler();
        euler.setFromQuaternion(quaternion, 'ZXY'); 
        // 从四元数中移除Z轴旋转（这里简化处理，实际上应使用更精确方法处理缩放和平移影响）
        const newQuaternion = new Quaternion().setFromEuler(new Euler(0, 0, 0));
        let out= new Matrix4()
        // const scale1 = new Vector3(1/110000,1/110000,1/110000);
        out.compose(position, newQuaternion, scale);
        return out
    }
    disposeChildren() {
        var self = this;
        self.childrenTiles.forEach(tile => tile.dispose());
        self.childrenTiles = [];
        if (!!self.meshContent) {
            self.meshContent.map(item => {
                const rootEntity = Engine.instance.scene.getEntity(item.id)
                if (!rootEntity) {
                    Engine.instance.scene.addEntity(rootEntity)
                }
            })
        }
    }
    updateCamera(camera) {
        const frustum = new Frustum();
        frustum.setFromProjectionMatrix(new Matrix4().multiplyMatrices(camera.project, camera.view));
        this._update(camera, frustum);
    }
    _update(camera, frustum) {
        const self = this;
        if (!self.isSetup) return;
        const visibilityBeforeUpdate = self.materialVisibility;
        if (!!self.boundingVolume && !!self.geometricError) {
            self.metric = self.calculateUpdateMetric(camera, frustum);
        }
        self.childrenTiles.forEach(child => child._update(camera, frustum));
        updateNodeVisibility(self.metric);
        updateTree(self.metric);
        trimTree(self.metric, visibilityBeforeUpdate);
        function updateTree(metric) {
            if (metric < 0 ) return;
            if (metric < 0 && self.hasMeshContent) return;
            if (!self.hasMeshContent || (metric < self.geometricErrorMultiplier * self.geometricError && self.meshContent.length > 0)) {
                if (!!self.json && !!self.json.children && self.childrenTiles.length != self.json.children.length) {
                    loadJsonChildren();
                    return;
                }
            }
        }
        function updateNodeVisibility(metric) {
            if (!self.hasMeshContent) return;
            if (self.meshContent.length < self.hasMeshContent) {
                return;
            }
            if (metric < 0) {
                self.inFrustum = false;
                self.changeContentVisibility(false);
                return;
            } else {
                self.inFrustum = true;
            }
            if (self.childrenTiles.length == 0) {
                self.changeContentVisibility(true);
                return;
            }
            if (metric >= self.geometricErrorMultiplier * self.geometricError) {
                self.changeContentVisibility(true);
            } else { // Ideal LOD is past this one
                if (self.refine == "REPLACE") {
                    let allChildrenReady = true;
                    self.childrenTiles.every(child => {
                        if (!child.isReady()) {
                            allChildrenReady = false;
                            return false;
                        }
                        return true;
                    });
                    if (allChildrenReady) {
                        self.changeContentVisibility(false);
                    }
                }
            }
        }
        function trimTree(metric) {
            if (!self.hasMeshContent) return;
            if (!self.inFrustum) { // 视锥体外
                self.disposeChildren();
                updateNodeVisibility(metric);
                return;
            }
            if (
                self.hasMeshContent &&
                self.meshContent.length > 0 &&
                !self.visible&&
                self._areAllChildrenLoadedAndHidden()) {
                self.disposeChildren();
                updateNodeVisibility(metric);
                return;
            }
            if (metric >= self.geometricErrorMultiplier * self.geometricError) {
                self.disposeChildren();
                updateNodeVisibility(metric);
                return;
            }

        }
        function loadJsonChildren() {
            // 简化children，没有作用的删除
            for (let i = self.json.children.length - 1; i >= 0; i--) {
                if (!self.json.children[i].root && !self.json.children[i].children && !self.json.children[i].getChildren && !self.json.children[i].content && !self.json.children[i].contents) {
                    self.json.children.splice(i, 1);
                }
            }
            self.json.children.forEach((childJSON, index) => {
                let childTile = new OGC3DTile({
                    parentTile: self,
                    parentGeometricError: self.geometricError,
                    parentBoundingVolume: self.boundingVolume,
                    parentRefine: self.refine,
                    json: childJSON,
                    rootPath: self.rootPath,
                    geometricErrorMultiplier: self.geometricErrorMultiplier,
                    loadOutsideView: self.loadOutsideView,
                    level: self.level + 1,
                    tileLoader: self.tileLoader,
                    cameraOnLoad: camera,
                    centerModel: false,
                    displayErrors: self.displayErrors,
                    index: index,
                });
                self.childrenTiles.push(childTile);
            });
        }
    }
    _areAllChildrenLoadedAndHidden() {
        let allLoadedAndHidden = true;
        this.childrenTiles.every(child => {
            if (child.hasMeshContent) {
                if (child.childrenTiles.length > 0) {
                    allLoadedAndHidden = false;
                    return false;
                }
                if (!child.inFrustum) {
                    return true;
                };
                if (!child.materialVisibility || child.meshDisplayed) {
                    allLoadedAndHidden = false;
                    return false;
                }
            } else {
                if (!child._areAllChildrenLoadedAndHidden()) {
                    allLoadedAndHidden = false;
                    return false;
                }
            }
            return true;
        });
        return allLoadedAndHidden;
    }

    /**
     * 如果节点在截锥体之外，如果它至少被绘制过一次，或者它的所有子节点都准备好了，那么该节点就准备好了
     * @returns true if ready
     */
    isReady() {
        if (!this.inFrustum) {
            return true;
        }
        if (this.hasUnloadedJSONContent) {
            return false;
        }
        if ((!this.hasMeshContent || this.meshContent.length == 0 || !this.materialVisibility)) {
            if (this.childrenTiles.length > 0) {
                var allChildrenReady = true;
                this.childrenTiles.every(child => {
                    if (!child.isReady()) {
                        allChildrenReady = false;
                        return false;
                    }
                    return true;
                });
                return allChildrenReady;
            } else {
                return false
            }
        }
        if (!this.hasMeshContent) {
            return true;
        }
        if (this.meshContent.length < this.hasMeshContent) {
            return false;
        }
        if (!this.materialVisibility) {
            return false;
        }
        if (this.meshDisplayed) {
            return true;
        }
        return false;
    }
    changeContentVisibility(visibility) {
        const self = this;
        if (self.hasMeshContent && self.meshContent.length > 0) {
            self.meshContent.forEach(mc => {
                if (mc.visibility !== visibility) {
                    mc.setVisibility(visibility)
                }
            })
        }
        self.visible=visibility
        if (self.materialVisibility == visibility) {
            return;
        }
        self.materialVisibility = visibility
        self.meshDisplayed = true;
    }
    calculateUpdateMetric(camera, frustum) {
        if (this.boundingVolume instanceof OBB) {
            // box
            tempSphere.copy(this.boundingVolume.sphere);
            tempSphere.applyMatrix4(this.worldMat);
            if (!frustum.intersectsSphere(tempSphere)) return -1;
        } else if (this.boundingVolume instanceof Sphere) {
            //sphere
            tempSphere.copy(this.boundingVolume);
            tempSphere.applyMatrix4(this.worldMat);
            if (!frustum.intersectsSphere(tempSphere)) return -1;
        } else {
            console.error("暂不支持其他形状");
            return -1
        }
        const distance = Math.max(0, camera.trans.position.distanceTo(tempSphere.center) - tempSphere.radius);
        if (distance == 0) {
            return 0;
        }
        // const scale = this.object3d.trans.matrixWorld.getMaxScaleOnAxis();
        const scale = this.worldMat.getMaxScaleOnAxis();
        rendererSize.x = Engine.instance.size[0]
        rendererSize.y = Engine.instance.size[1]
        let s = rendererSize.y;
        let fov = camera.fovy;
        if (camera.aspect < 1) {
            fov *= camera.aspect;
            s = rendererSize.x;
        }
        let lambda = 2.0 * Math.tan(0.5 * fov) * distance;
        return (window.devicePixelRatio * 16 * lambda) / (s * scale);
    }
    getSiblings() {
        const self = this;
        const tiles = [];
        if (!self.parentTile) return tiles;
        let p = self.parentTile;
        while (!p.hasMeshContent && !!p.parentTile) {
            p = p.parentTile;
        }
        p.childrenTiles.forEach(child => {
            if (!!child && child != self) {
                while (!child.hasMeshContent && !!child.childrenTiles[0]) {
                    child = child.childrenTiles[0];
                }
                tiles.push(child);
            }
        });
        return tiles;
    }
    calculateDistanceToCamera(camera) {
        if (this.boundingVolume instanceof OBB) {
            // box
            tempSphere.copy(this.boundingVolume.sphere);
            // tempSphere.applyMatrix4(this.object3d.trans.matrixWorld);
            tempSphere.applyMatrix4(this.worldMat);
            //if (!frustum.intersectsSphere(tempSphere)) return -1;
        } else if (this.boundingVolume instanceof Sphere) {
            //sphere
            tempSphere.copy(this.boundingVolume);
            // tempSphere.applyMatrix4(this.object3d.trans.matrixWorld);
            tempSphere.applyMatrix4(this.worldMat);
            //if (!frustum.intersectsSphere(tempSphere)) return -1;
        }
        else {
            console.error("unsupported shape")
        }
        let distance = Math.max(0, camera.trans.position.distanceTo(tempSphere.center) - tempSphere.radius)
        return distance;
    }
    setGeometricErrorMultiplier(geometricErrorMultiplier) {
        this.geometricErrorMultiplier = geometricErrorMultiplier;
        this.childrenTiles.forEach(child => child.setGeometricErrorMultiplier(geometricErrorMultiplier));
    }

    transformWGS84ToCartesian(lon, lat, h, sfct) {
        const a = 6378137.0;
        const e = 0.006694384442042;
        const N = a / (Math.sqrt(1.0 - (e * Math.pow(Math.sin(lat), 2))));
        const cosLat = Math.cos(lat);
        const cosLon = Math.cos(lon);
        const sinLat = Math.sin(lat);
        const sinLon = Math.sin(lon);
        const nPh = (N + h);
        const x = nPh * cosLat * cosLon;
        const y = nPh * cosLat * sinLon;
        const z = (0.993305615557957 * N + h) * sinLat;

        sfct.set(x, y, z);
    }
}
export { OGC3DTile };

function showError(error) {
    var errorDiv = document.createElement("div");
    errorDiv.textContent = error;
    errorDiv.style.position = 'fixed';
    errorDiv.style.top = '10px';
    errorDiv.style.left = '50%';
    errorDiv.style.transform = 'translateX(-50%)';
    errorDiv.style.padding = '10px';
    errorDiv.style.backgroundColor = '#ff8800';
    errorDiv.style.color = '#ffffff';
    errorDiv.style.zIndex = '9999';
    document.body.appendChild(errorDiv);
    setTimeout(function () {
        errorDiv.remove();
    }, 8000);
}
